- Görkem Güray/
- 100 Günde SwiftUI Notları/
- 9.Gün - Swift Fonksiyonlar - 3 : Closure Nedir? Nasıl Çalışır? Closure Örnekleri/
9.Gün - Swift Fonksiyonlar - 3 : Closure Nedir? Nasıl Çalışır? Closure Örnekleri
Table of Contents
Fonksiyonun Değişkene Atanması #
Fonksiyonları değişkenlere atayabilir, fonksiyonları fonksiyonlara parametre olarak geçebilir veya fonksiyonlardan fonksiyonları döndürebiliriz.
func greetUser() {
print("Hi there!")
}
greetUser()
var greetCopy = greetUser
greetCopy()
//ÇIKTI:
//----------------------------------------
// Hi there!
// Hi there!
Yukarıdaki örnekte bir fonksiyonun kendisi, bir değişkene atanmıştır.
Önemli : Fonksiyon, değişkene atanırken ()
-parantezler- kullanılmaz. Parantezleri kullandığımızda fonksiyonu çağırmış oluruz ve fonksiyonun dönüş değeri değişkene atanır.
Closure Tanımlanması #
Yukarıda fonksiyonun kendisinin bir değişken (veya sabit)’e atanabileceğini gördük. Peki ya fonksiyon tanımlama adımını atlayıp, fonksiyonun yaptığı işi doğrudan bir değişkene atayamaz mıyız? İşte Swift bu işleme closure adını vermekte.
let sayHello = {
print("Hi there!")
}
sayHello()
//ÇIKTI:
//----------------------------------------
// Hi there!
Yukarıda closure kullanılarak oluşturulmuş bir fonksiyon ve bu fonksiyonun atandığı bir sabit görüyoruz. Fonksiyon çağrısını yapabilmek için sabitin ()
parantezlerini kullanarak yazıldığına dikkat edelim. Yukarıdaki closure herhangi bir parametre almamış ve geri dönüş değeri de yoktur. Şimdide bunları barındıran bir closure yazalım.
let sayHello = { (name: String) -> String in
"Hi \(name)!"
}
closure, { }
parantezleri ile başlar ve biter. Bu sebeple parametreleri ve geri dönüş değer tipini bildirdiğimiz yer de bu parantezler içi olmalıdır. Yukarıdaki örnekte gördüğümüz in
anahtar kelimesi ise parametre ve geri dönüş tipi bildiriminin sonunu işaretlemek için kullanılır.
Parametre Almayan, Değer Döndüren Closure Tanımlanması #
let payment = { () -> Bool in
print("Paying an anonymous person…")
return true
}
Closure ‘ın hiç parametre almadığını belirtmek için ()
boş parantezleri kullanırız. func payment() -> Bool
yazmaya oldukça benziyor.
Fonksiyonların Türü #
Swift’te, tamsayıların Int
türüne ondalık sayıların Double
türüne sahip olduğu gibi, fonksiyonlar da bir türe (type) sahiptir.
Yukarılardaki örnekte yazdığımız parametre almayan ve bir geri dönüşü bulunmayan greetUser()
fonksiyonunu type annotation kullanarak greetCopy
değişkenine atayalım.
var greetCopy: () -> Void = greetUser
Yukarıdaki kodu biraz inceleyelim;
- Boş parantezler
()
parametre almayan bir fonksiyonu işaret eder. ->
fonksiyon oluştururken kullanılan anlamdadır, yani geri dönüş türünü bildireceğiz.Void
hiçbir şey anlamına gelir. Yani bu fonksiyonun geri dönüş değeri yoktur. Bazen Void yerine()
yazıldığını görebiliriz.
Her fonksiyonun türü, aldığı ve geri gönderdiği verilere bağlıdır. Burada önemli bir püf noktası var: aldığı verilerin isimleri, fonksiyonun türünün bir parçası değildir. Bir örnek daha yapalım;
func getUserData(for id: Int) -> String {
if id == 1989 {
return "Taylor Swift"
} else {
return "Anonymous"
}
}
let data: (Int) -> String = getUserData
let user = data(1989)
print(user)
Int
türünde bir veri alan, geri dönüş tipi String
olan getUserData
fonksiyonunu , data
sabitine atayalım. data fonksiyonunu çağırırken, data(for: 1989)
yazmak yerine, data(1989)
yazdık. Çünkü fonksiyon türü harici parametre adını içermez.
Bu durum closure için de geçerlidir. Daha önce tanımladığımız sayHello
closure ‘unun çağrısını şimdi yapalım.
let sayHello = { (name: String) -> String in
"Hi \(name)!"
}
sayHello("Taylor")
//ÇIKTI:
//----------------------------------------
// Hi Taylor!
sayHello
closure ‘ı tıpkı fonksiyonların kopyalanmasında olduğu gibi, parametre adı kullanmaz. Yani harici parametre adları yalnızca bir fonksiyonu doğrudan çağırdığımızda önemlidir, bir closure oluşturduğumuzda veya fonksiyonun kopyasını aldığımızda değil.
Closure Kullanımı #
let team = ["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha"]
let sortedTeam = team.sorted()
print(sortedTeam)
team
Array’i harika bir şekilde sıralanacaktır. Fakat bu sıralamayı kontrol etmek istesek nasıl yapacağız? Örneğin, takım kaptanının adının en başta olmasını, ardından isimlerin alfabetik olarak sıralanmasını isteyelim. İşte sorted()
bu durumu kontrol etmek için özel bir sıralama fonksiyonu geçmemize izin verir. Bu fonksiyon, iki adet String
’i parametre olarak alır ve eğer ilk String daha önce sıralanıyorsa true
değilse false
döndürür. Eğer Suzanne kaptansa yazacağımız kod şu şekilde olmalıdır.
func captainFirstSorted(name1: String, name2: String) -> Bool {
if name1 == "Suzanne" {
return true
} else if name2 == "Suzanne" {
return false
}
return name1 < name2
}
Yani name1
Suzanne ise true dönecek çünkü name1
name2
’den önce geldiği için true olmalıdır. Fakat name2
Suzanne ise false dönecek çünkü, name1
name2
’den sonraya sıralanmalıdır. Eğer hiçbir isim Suzanne değilse, <
ile normal alfabetik sıralama yapılacaktır.
Bu sayede captainFirstSorted
fonksiyonunu, sorted()
fonksiyonuna parametre olarak geçebiliriz.
let captainFirstTeam = team.sorted(by: captainFirstSorted)
print(captainFirstTeam)
//ÇIKTI:
//----------------------------------------
// ["Suzanne", "Gloria", "Piper", "Tasha", "Tiffany"]
sorted()
fonksiyonu iki String kabul eden ve bir Boolean dönen bir fonksiyon ister. Bu fonksiyonun func
kullanılarak mı tanımlandığı yoksa bir closure ‘mı olduğunun bir önemi yoktur.
sorted()
fonksiyonunu closure kullanarak yeniden yazacağız.
let captainFirstTeam = team.sorted(by: { (name1: String, name2: String) -> Bool in
if name1 == "Suzanne" {
return true
} else if name2 == "Suzanne" {
return false
}
return name1 < name2
})
Yukarıdaki kodu biraz inceleyelim;
- Daha önce olduğu gibi
sorted()
fonksiyonunu çağırıyoruz. - Bir fonksiyon geçmek yerine, bir closure yazıyoruz.
sorted()
fonksiyonunun bize aktaracağı iki String parametresini listeliyoruz, closure’ın bir Boolean döndüreceğini söylüyor ve closure kodunun başlangıcınıin
kullanarak işaretliyoruz.- Geri kalan her şey normal fonksiyon kodudur.
Closure Kısa Syntax #
Yukarıda oluşturduğumuz kodu biraz daha derli toplu tekrar buraya yazalım.
let team = ["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha"]
let captainFirstTeam = team.sorted(by: { (name1: String, name2: String) -> Bool in
if name1 == "Suzanne" {
return true
} else if name2 == "Suzanne" {
return false
}
return name1 < name2
})
print(captainFirstTeam)
sorted()
fonksiyonu için iki String alan ve bir Boolean döndüren bir fonksiyon sağlanmalıdır. Öyleyse closure içinde neden aynı şeyleri tekrarlıyoruz.
Closure ‘ı kısa olarak yazmak için, alınan parametre ve geri dönüş türünü belirtmemize gerek yok.
Bu sebeple kodu şu şekilde yazabiliriz.
let captainFirstTeam = team.sorted(by: { name1, name2 in
Şimdi kodu biraz daha azaltmamızın bir yolu var. sorted()
gibi fonksiyon kabul eden fonksiyonlar trailing closure syntax olarak adlandırılan yazım biçimine izin verir.
let captainFirstTeam = team.sorted { name1, name2 in
if name1 == "Suzanne" {
return true
} else if name2 == "Suzanne" {
return false
}
return name1 < name2
}
Trailing closure syntax sayesinde, closure’ı parametre olarak geçmek yerine, devam edip closure ‘ı başlatıyoruz. Bunu yaparken, sorted
fonksiyonunun ()
parantezlerini ve parametre ismini kaldırıyoruz.
Swift’te closure ‘u sadeleştirebilecek son bir adım daha var. Swift closure içindeki parametre adlarını otomatik olarak sağlayabilir. Bu durumda name1
ve name2
yerine $0
ve $1
kullanabiliriz.
let captainFirstTeam = team.sorted {
if $0 == "Suzanne" {
return true
} else if $1 == "Suzanne" {
return false
}
return $0 < $1
}
Closure Kullanımı ile İlgili Örnekler #
sorted()
fonksiyonu ile bu sefer sırlamayı tersine çevirelim.
let reverseTeam = team.sorted { $0 > $1 }
filter()
fonksiyonu ile her bir Array elemanı üzerinde bazı kontroller yapabiliriz. Bu kontroller neticesinde true olan öğeler yeni bir Array olarak döndürülür. Örneğin, takımdaki oyunculardan isminin baş harfi T olan oyuncuları şu şekilde alabiliriz.
let tOnly = team.filter { $0.hasPrefix("T") }
print(tOnly)
//ÇIKTI:
//----------------------------------------
// ["Tiffany", "Tasha"]
map()
fonksiyonu, Array’de bulunan her bir elemanı dönüştürmemizi sağlar. İsteğimize göre dönüştürülmüş Array’i geri döndürür. Aşağıdaki örnekte Array’deki tüm elemanların büyük harfli hali geri dönüş olarak alınmıştır.
let uppercaseTeam = team.map { $0.uppercased() }
print(uppercaseTeam)
//ÇIKTI:
//----------------------------------------
// ["GLORIA", "SUZANNE", "PIPER", "TIFFANY", "TASHA"]
Not: map()
fonksiyonu ile çalışırken, döndürdüğümüz türün, başladığımız tür ile aynı olması gerekmez. Örneğin, Int
olarak aldığımız veriyi String
olarak geri döndürebiliriz.
Fonksiyonlar Parametre Olarak Nasıl Kabul Edilir? #
Closure kullanılabilmesi için, fonksiyonun parametre olarak fonksiyon alabilmesi gerekmektedir. Bu başlık altında bunun nasıl yapılabileceğini inceleyeceğiz.
Daha önce aşağıdaki kodu görmüştük;
func greetUser() {
print("Hi there!")
}
greetUser()
var greetCopy: () -> Void = greetUser
greetCopy()
greetCopy
değişkeni tanımlanırken type annotation kasıtlı olarak eklenmiştir. Çünkü fonksiyonları parametre olarak belirtirken tam olarak bunu kullanıyoruz. Swift’e fonksiyonun hangi parametreleri kabul ettiğini ve dönüş tipini söylüyoruz.
func makeArray(size: Int, using generator: () -> Int) -> [Int] {
var numbers = [Int]()
for _ in 0..<size {
let newNumber = generator()
numbers.append(newNumber)
}
return numbers
Yukarıdaki kodu inceleyelim;
- Fonksiyonun adı
makeArray()
BiriInt
olmak üzere iki parametre alır ve birInt
Array döndürür. - İkinci parametre bir fonksiyondur. Parametre olan fonksiyonun kendisi parametre kabul etmez, ancak her çağrıldığında bir tamsayı döndürür.
makeArray()
içinde boş birInt
Array oluştururuz ve istenildiği kadar döngü yaparız.- Döngünün her yinelenmesinde, parametre olarak aktarılan
generator
fonksiyonunu çağrırız. Bu da bize yeni bir tamsayı döndürecektir. Bu dönen tamsayıyınumbers
Array’ine ekleriz. - En sonunda tamamlanan Array’i geri döndürürüz.
Buradaki en karmaşık kısım aslında ilk satırdır:
func makeArray(size: Int, using generator: () -> Int) -> [Int] {
Yukarıdaki kodu soldan itibaren okursak;
- Yeni bir fonksiyon oluştururuz.
- Bu fonksiyonun adı
makeArray()
’dir. - İlk parametre bir
Int
olansize
’dır. - İkinci parametre, kendisi parametre kabul etmeyen ve bir tamsayı döndüren
generator
adlı fonksiyondur. - Sonuç olarak
makeArray()
Int Array’i döndürür.
Yukarıda oluşturduğumuz fonksiyonu kullanalım
let rolls = makeArray(size: 50) {
Int.random(in: 1...20)
}
print(rolls)
Yukarıdaki kodu closure kullanmadan şu şekilde de yazabiliriz.
func generateNumber() -> Int {
Int.random(in: 1...20)
}
let newRolls = makeArray(size: 50, using: generateNumber)
print(newRolls)
Birden Fazla Parametre Olarak Fonksiyon Alan Fonksiyonlar #
Bu durumu göstermek için, her biri parametre kabul etmeyen ve hiçbir şey geri döndürmeyen parametre olarak fonksiyon alan bir fonksiyon oluşturalım
func doImportantWork(first: () -> Void, second: () -> Void, third: () -> Void) {
print("About to start first work")
first()
print("About to start second work")
second()
print("About to start third work")
third()
print("Done!")
}
Bu fonksiyonu closure ile çağırmak istediğimizde, ilk çağrılacak closure daha önce yaptığımız gibi çağrılır. Fakat ikinci ve üçüncü closure için harici parametre adını yazıp ardından closure açmamız gerekir.
doImportantWork {
print("This is the first work")
} second: {
print("This is the second work")
} third: {
print("This is the third work")
}
//ÇIKTI:
//----------------------------------------
//About to start first work
//Doing first thing
//About to start second work
//Doing second thing
//About to start third work
//Doing third thing
//Done!
Sonuç #
Bu yazıda Swift Fonksiyonlarının son bölümü olan Closure konusunu tamamladık. Özetlemek gerekirse;
- Swift’te fonksiyonları kopyalayabiliriz, harici parametre adlarını kaybetmeleri dışında orijinaliyle aynı şekilde çalışırlar.
- Tüm fonksiyonların veri türü vardır. (Tamsayının
Int
, ondalıklı sayınınDouble
olması gibi). Bu veri türü, aldıkları parametrelerin tipi ve dönüş tipidir. - Bir sabite veya değişkene atama yaparak closure oluşturabiliriz.
- Parametre alan veya bir değer döndüren closure ‘larda bu işlem
{ }
parantezleri içinde yapılmalı vein
ile işaretlenmelidir. - Fonksiyonlar, diğer fonksiyonları parametre olarak kabul edebilir. Parametre olan fonksiyonların tam olarak hangi değeleri alıp döndüreceği belirlenmelidir.
- Bu durumda özel bir fonksiyonu geçmek yerine bir closure oluşturabiliriz. Swift her iki yaklaşımı da kabul eder.
- Bir fonksiyonun son parametrelerinden biri veya daha fazlası fonksiyon ise, trailing closure syntax kullanabiliriz.
- Closure içinde
$0
ve$1
gibi kısaltılmış parametre adaları kullanabiliriz.
100 Days of SwiftUI Checkpoint - 5 #
Bu yazıyı İngilizce olarak da okuyabilirsiniz.
You can also read this article in English.